package com.yn.xgame;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Predicate;

/**
 * 定时器管理 - 俄罗斯轮盘
 * 这是线程不安全的类，请务必在线程内使用
 * Created by wangboo on 2017/7/11.
 */
public class WheelLikeTimerManager implements ITimerManager {

    private static class Timer implements Comparable {
        long id;
        Callback callback;
        long nextTick;
        Timer(long id, long nextTick, Callback callback) {
            this.id = id;
            this.nextTick = nextTick;
            this.callback = callback;
        }

        @Override
        public int compareTo(Object o) {
            return (int) (nextTick - ((Timer)o).nextTick);
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Timer) {
                return id == ((Timer) o).id;
            }
            return false;
        }
    }

    private static class RepeatedTimer extends Timer {
        long during;
        RepeatedTimer(long id, long nextTick, long during, Callback callback) {
            super(id, nextTick, callback);
            this.during = during;
        }
    }

    private static final Logger logger = LoggerFactory.getLogger(WheelLikeTimerManager.class);
    private SortedSet<Timer> set = new TreeSet<>();
    private SortedSet<Timer> adding = new TreeSet<>();
    private SortedSet<Timer> removing = new TreeSet<>();
    private static long serialNo = 0;

    @Override
    public void clockTick(long clock) {
        if (adding.size() > 0) {
            set.addAll(adding);
            adding.clear();
        }
        if(removing.size() > 0) {
            set.removeAll(removing);
            removing.clear();
        }
        for (Timer t : set) {
            if (t.nextTick < clock) {
                try {
                    t.callback.call();
                } catch (Throwable e) {
                    logger.error("tick error:", e);
                } finally {
                    if (t instanceof RepeatedTimer) {
                        t.nextTick += ((RepeatedTimer) t).during;
                    } else {
                        removing.add(t);
                    }
                }
            } else {
                break;
            }
        }
        if (removing.size() > 0) {
            removing.forEach(t -> set.removeIf(t1 -> t1.id == t.id));
        }
    }

    @Override
    public long tickOnce(long after, Callback callback) {
        long id = serialNo++;
        adding.add(new Timer(id, after + System.currentTimeMillis(), callback));
        return id;
    }

    @Override
    public long tickForever(long after, long during, Callback callback) {
        long id = serialNo++;
        adding.add(new RepeatedTimer(id, after + System.currentTimeMillis(), during, callback));
        return id;
    }

    @Override
    public boolean cancelTick(long id) {
        Predicate<Timer> pre = t -> t.id == id;
        boolean removed = adding.removeIf(pre);
        return removed || set.removeIf(pre);
    }

    @Override
    public int size() {
        return set.size();
    }

}
